package com.icekrvams.billing

import android.app.Activity
import android.app.Application
import android.content.Context
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.ProcessLifecycleOwner
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.icekrvams.billing.data.DataHolder
import com.icekrvams.billing.data.Products
import com.icekrvams.billing.listeners.BillingDataResponseListener
import com.icekrvams.billing.listeners.BillingListener
import com.icekrvams.billing.listeners.ListenerHolder
import com.icekrvams.billing.listeners.PurchaseConfirmListener
import com.icekrvams.billing.utilities.BillingUtilities
import com.icekrvams.billing.utilities.dLog
import com.icekrvams.billing.utilities.eLog
import com.icekrvams.billing.utilities.iLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

object Billing {

    val billingClient: BillingClient
        get() = BillingCore.billingClient
    var logEnabled = false // 是否开启日志。
    internal lateinit var app: Application

    // 已拥有商品信息
    private val purchases: List<Purchase> get() = DataHolder.purchases
    private val workerCoroutineScope = CoroutineScope(Dispatchers.IO)
    private var productDetailsRefreshJob: Job? = null

    /**
     * 保存已拥有的商品的 LiveData。
     * 由于初始化时会立即将本地保存的状态设置给 ownedProductsLiveData，所以调用 observe 方法时，回调方法会立即被调用。
     * 已拥有的商品发生变化时，回调方法也会被调用。
     * @sample
     *  Billing.ownedProductsLiveData.observe(lifecycleOwner) { ownedProducts ->
     *      if (ownedProducts.contains("product")) {
     *          // Do something when product owned.
     *      } else {
     *
     *      }
     *  }
     *
     */
    val ownedProductsLiveData: LiveData<List<String>>
        get() = DataHolder.ownedProductsLiveData

    /**
     * 已拥有的商品（包含一次性内购商品和订阅商品）。
     */
    val ownedProducts: List<String>
        get() = DataHolder.ownedProducts

    /**
     * 判断是否已经拥有目标商品。
     * @param product 商品 Id。
     */
    fun isProductOwned(product: String): Boolean {
        return DataHolder.isProductOwned(product)
    }

    /**
     * 获取商品展示价格。
     */
    fun getFormattedPrice(product: String): String? {
        return BillingUtilities.getFormattedPrice(product)
    }

    /**
     * 获取商品价格。
     */
    fun getPrice(product: String): Double? {
        return BillingUtilities.getPrice(product)
    }

    /**
     * 获取 Billing API 返回的商品价格。该价格是实际价格的 1000000 倍。
     */
    fun getOriginalPrice(product: String): Long? {
        return BillingUtilities.getOriginalPrice(product)
    }

    /**
     * 获取商品展示价格货币单位。
     */
    fun getPriceCurrencyCode(product: String): String? {
        return BillingUtilities.getPriceCurrencyCode(product)
    }

    /**
     * 获取商品详细信息。
     */
    fun getProductDetails(product: String): ProductDetails? {
        return DataHolder.productWithProductDetails[product]
    }

    /**
     * 必须做为 Billing 组件中被调用的第一个方法。
     * 建议在 Application#onCreate() 中调用。
     * @param products 所有商品 Id。
     */
    @JvmOverloads
    fun init(
        app: Application,
        products: Products,
        billingListener: BillingListener? = null
    ) {
        this.app = app
        DataHolder.initialize(products)
        billingListener?.let {
            ListenerHolder.addPurchaseConfirmListener(it, null)
            ListenerHolder.addBillingDataResponseListener(it, null)
        }
        ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                if (BillingCore.isBillingClientReady()) {
                    workerCoroutineScope.launch {
                        "Query purchases on application resume".dLog()
                        BillingCore.queryPurchases()
                    }
                }
            }
        })
    }

    /**
     * 应用主购买起始入口，一般为 MainActivity。
     * @param mainActivity 应用主 Activity，BillingClient 会在其 onDestroy 时终止连接。
     */
    fun mainEntrance(mainActivity: AppCompatActivity) {
        BillingCore.ensureBillingClientConnection()
        mainActivity.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onDestroy(owner: LifecycleOwner) {
                BillingCore.endConnection()
            }
        })
    }

    /**
     * 次级购买起始入口。
     * 次级页面可通过用户点击通知、深度链接等方式直接打开时，它也有可能作为发起购买的起始入口。
     * 不需要担心对 billingEntrance() 进行重复调用，发生重复调用时，不会重复初始化或者连接 BillingClient。
     */
    fun secondaryEntrance() {
        BillingCore.ensureBillingClientConnection()
    }

    /**
     * 起草一个订阅流程，对订阅项进行检查，并将检查结果回调给 [billingFlowDraftCallback]。
     * @param product 商品 ID。
     * @param lifecycleOwner 当未找到 [product] 对应的商品时，会进行一次商品信息的重新查询，用于在当前组件
     * 生命周期结束时将该重试任务取消。
     * @param subOfferToken [ProductDetails.SubscriptionOfferDetails.offerToken] 订阅商品优惠。
     * @param billingFlowDraftCallback 对当前商品的检查结果回调。
     */
    @JvmOverloads
    fun draftBillingFlow(
        product: String,
        lifecycleOwner: LifecycleOwner,
        billingFlowDraftCallback: BillingFlowDraftCallback,
        subOfferToken: String? = null,
        forceLaunchBillingFlow: Boolean = false
    ) {
        if (!DataHolder.products.allAvailableProducts.contains(product)) {
            throw IllegalStateException("$product is not an available product")
        }
        val isSubProduct = DataHolder.products.availableSubProducts.contains(product)
        if (!isSubProduct && subOfferToken != null) {
            throw IllegalStateException("Only sub products need a offer token.")
        }
        lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onDestroy(owner: LifecycleOwner) {
                // Lifecycle owner 销毁时，取消商品信息查询任务（如果有的话）。
                productDetailsRefreshJob?.let {
                    if (it.isActive) {
                        it.cancel()
                        "Product details refresh job canceled.".dLog()
                    }
                }
            }
        })
        // 检索已经拥有的订阅商品。
        val ownedSubscriptions = BillingUtilities.deviceOwnedSubscriptions()
        when {
            // Iap 或者不拥有任何订阅商品时进行订阅，进行普通购买流程。
            !isSubProduct || ownedSubscriptions.isEmpty() -> {
                continueDraftingBillingFlow(product, subOfferToken, null, billingFlowDraftCallback)
            }
            // 已经拥有一件订阅商品，此时进行订阅。
            ownedSubscriptions.size == 1 -> {
                val isTargetProductOwned = ownedSubscriptions.contains(product)
                if (!forceLaunchBillingFlow && isTargetProductOwned) {
                    // 目标商品已经订阅过，不能重复订阅，建议引导用户跳转到PlayStore订阅管理页面。
                    billingFlowDraftCallback.onShouldGoToSubManagePage(ownedSubscriptions)
                    billingFlowDraftCallback.onProductAlreadyOwned(ownedSubscriptions.first())
                } else {
                    continueDraftingBillingFlow(
                        product,
                        subOfferToken,
                        ownedSubscriptions.first(),
                        billingFlowDraftCallback,
                        forceLaunchBillingFlow
                    )
                }
            }
            // 已经存在至少两个，建议引导用户跳转到PlayStore订阅管理页面。
            else -> {
                billingFlowDraftCallback.onShouldGoToSubManagePage(ownedSubscriptions)
            }
        }
    }

    /**
     * 启动购买流程，应该先调用[draftBillingFlow]，并在
     * [BillingFlowDraftCallback.onReadyToBuy] 中调用此方法。
     * @param activity 用于启动购买页面的 Activity。
     * @param params 购买流程参数。
     */
    @UiThread
    fun launchBillingFlow(
        activity: Activity,
        params: BillingFlowParams
    ): BillingResult {
        if (!BillingCore.billingClient.isReady) {
            "launchBillingFlow: BillingClient is not ready".eLog()
        }
        val billingResult = BillingCore.billingClient.launchBillingFlow(activity, params)
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        "launchBillingFlow: BillingResponse $responseCode $debugMessage".dLog()
        return billingResult
    }

    /**
     * 添加 Billing 数据响应结果回调。
     * @param lifecycleOwner 当前 LifecycleOwner，用于在其生命周期结束时，自动移除监听器。
     */
    fun addBillingDataResponseListener(
        billingDataResponseListener: BillingDataResponseListener,
        lifecycleOwner: LifecycleOwner
    ) = ListenerHolder.addBillingDataResponseListener(billingDataResponseListener, lifecycleOwner)

    /**
     * 添加商品确认（或消耗）结果的监听器。
     * @param lifecycleOwner 当前 LifecycleOwner，用于在其生命周期结束时，自动移除监听器。
     */
    fun addPurchaseConfirmListener(
        purchaseConfirmListener: PurchaseConfirmListener,
        lifecycleOwner: LifecycleOwner
    ) = ListenerHolder.addPurchaseConfirmListener(purchaseConfirmListener, lifecycleOwner)

    /**
     * 启动 PlayStore 订阅管理页面，常用于出现重复订阅时。
     * @see BillingFlowDraftCallback.onShouldGoToSubManagePage
     */
    @JvmOverloads
    fun openPlayStoreSubManagementPage(
        context: Context,
        product: String? = null
    ) {
        BillingUtilities.openPlayStoreSubManagementPage(context, product)
    }

    /**
     * 起草订阅流程。
     */
    private fun continueDraftingBillingFlow(
        product: String,
        offerToken: String?,
        oldProduct: String?,
        billingFlowDraftCallback: BillingFlowDraftCallback,
        forceLaunchBillingFlow: Boolean = false
    ) {
        // 首先判断是否已经购买了目标商品。
        if (BillingUtilities.deviceIsPurchaseOwned(purchases, product) && !forceLaunchBillingFlow) {
            "You cannot buy a one-time product that is already owned:$product. Please check if it's owned before purchasing".eLog()
            billingFlowDraftCallback.onProductAlreadyOwned(product)
            return
        }
        // 订阅：判断是否可以替换已有的商品（根据传入的 oldProduct）。
        var oldProductToBeReplaced: String? = null
        val isSubProduct = DataHolder.products.availableSubProducts.contains(product)
        if (isSubProduct && BillingUtilities.isOldProductReplaceable(purchases, oldProduct)) {
            oldProductToBeReplaced = oldProduct
        }
        when {
            oldProduct == null -> "Regular purchase."
            product == oldProduct -> "Re-subscribe."
            else -> "Subscription changing from $oldProduct to $product"
        }.iLog()
        // 根据商品 Id，获取商品详细信息。
        val productDetails = DataHolder.productWithProductDetails[product]
        if (productDetails != null) {
            continueBuying(
                product = product,
                offerToken = offerToken,
                oldProductToBeReplaced = oldProductToBeReplaced,
                productDetails = productDetails,
                billingFlowDraftCallback = billingFlowDraftCallback
            )
        } else {
            refreshProductDetails(
                product = product,
                offerToken = offerToken,
                oldProductToBeReplaced = oldProductToBeReplaced,
                billingFlowDraftCallback = billingFlowDraftCallback
            )
        }
    }

    /**
     * 未找到当前商品信息，尝试重新查询商品信息。
     */
    private fun refreshProductDetails(
        product: String,
        offerToken: String?,
        oldProductToBeReplaced: String?,
        billingFlowDraftCallback: BillingFlowDraftCallback
    ) {
        // 未找到 product 对应的 ProductDetails 尝试重新查询商品信息。
        "ProductDetails not found.Try refreshing...".eLog()
        billingFlowDraftCallback.onRefreshProductDetailsStateChanged(true)
        productDetailsRefreshJob = workerCoroutineScope.launch {
            val productDetailsResult = BillingCore.queryProductDetails()
            val billingResult = productDetailsResult.billingResult
            val productWithProductDetails =
                productDetailsResult.productDetailsList?.associateBy { it.productId }
            // 查询结束后，重新获取 product 对应的商品信息。
            val refreshedProductDetails = productWithProductDetails?.get(product)
            withContext(Dispatchers.Main) {
                billingFlowDraftCallback.onRefreshProductDetailsStateChanged(false)
                // 请求完成，且成功查询到了目标商品信息。
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && refreshedProductDetails != null) {
                    "ProductDetails refreshed and found, continue buying.".dLog()
                    continueBuying(
                        product = product,
                        offerToken = offerToken,
                        oldProductToBeReplaced = oldProductToBeReplaced,
                        productDetails = refreshedProductDetails,
                        billingFlowDraftCallback = billingFlowDraftCallback
                    )
                } else {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        // 虽然查询成功，但依旧没找到对应目标商品的信息。
                        "ProductDetails refreshed, but still not found".eLog()
                    } else { // 本次查询失败。
                        "ProductDetails refresh failed.code:${billingResult.responseCode}, message:${billingResult.debugMessage}".eLog()
                    }
                    billingFlowDraftCallback.onProductNotFound(product)
                }
            }
        }
    }

    /**
     * 前期检查通过，继续购买流程。
     * @param product 目标商品 Id。
     * @param oldProductToBeReplaced 可被替换的商品 Id。
     */
    private fun continueBuying(
        product: String,
        offerToken: String?,
        oldProductToBeReplaced: String?,
        productDetails: ProductDetails,
        billingFlowDraftCallback: BillingFlowDraftCallback
    ) {
        val availableOfferToken =
            BillingUtilities.getAvailableOfferToken(productDetails, offerToken)
        val isSubProduct = DataHolder.products.availableSubProducts.contains(product)
        if (isSubProduct && availableOfferToken == null) {
            // 订阅商品的 offerToken 为空，建议跳转到 PlayStore 订阅管理页面进行处理。
            billingFlowDraftCallback.onShouldGoToSubManagePage(listOf(product))
            "Offer Token unavailable for product:$product".eLog()
            return
        }
        val productDetailsParams =
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(productDetails)
        if (availableOfferToken != null) {
            productDetailsParams.setOfferToken(availableOfferToken)
        }
        val productDetailsParamsList = listOf(productDetailsParams.build())
        val billingFlowParamsBuilder =
            BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParamsList)
        if (oldProductToBeReplaced != null && oldProductToBeReplaced != product) {
            BillingUtilities.purchaseForProduct(purchases, oldProductToBeReplaced)?.apply {
                billingFlowParamsBuilder.setSubscriptionUpdateParams(
                    BillingFlowParams.SubscriptionUpdateParams.newBuilder()
                        .setOldPurchaseToken(purchaseToken).build()
                )
            }
        }
        val billingParams = billingFlowParamsBuilder.build()
        // 将参数返回给 Activity，以启动 Google Play 购买页面。
        billingFlowDraftCallback.onReadyToBuy(billingParams)
    }
}
